using System;
using System.Collections.Generic;
using System.Html;
using jQueryApi;
using System.Collections;
using System.Diagnostics;
using SharpLZW;


namespace TuringMachine
{
   public delegate void MessageHandler(object sender, string message);


   public class StateBoard
   {
      public int BOARD_SIZE_X = 20;
      public int BOARD_SIZE_Y = 12;
      public const int OP_COMPARE_CONST = 0;
      public const int OP_WRITE_CONST = 1;
      public const int OP_TAPE_FWD = 2;
      public const int OP_TAPE_BACK = 3;
      public const int OP_RED_TAPE = 4;
      public const int OP_WHITE_TAPE = 5;
      public const int OP_COMPARE_TAPES = 6;
      public const int OP_ACCEPT = 7;
      public const int OP_FLIP_BIT = 8;
      public const int OP_WRITE_VAL_TAPE = 9;
      public const int OP_FUNCTION = 10;
      public const int OP_INSERTCELL = 11;
      public const int OP_REMOVECELL = 12;

      public const int DISTANCE_CELL = 36;
      public const int MAX_OP = 13;
      public const int MAX_CH = 4;
      public const int MAX_DIR = 16;

      internal Upgrades m_abilities;

      StateCell[][] cells;
      StateCell[][] m_selection;
      StateCell m_selectedCell = null;
      StateCell m_executionPoint = null;
      Stack<StateCell>[] m_returnPoint;
      StateCell[] m_lastCalled;

      private event MessageHandler OnDebugEnd;
      public event EventHandler OnLevelCompleted;
      public event EventHandler OnLevelAbandoned;

      CellEditor m_editor;
      Div m_frameSelector = null;
      Div m_selectionMirror = null;
      Div m_movementBackground = null;
      int m_selFromX = -1;
      int m_selFromY = -1;
      int m_selToX = -1;
      int m_selToY = -1;
      int m_pinSelectionX;
      int m_pinSelectionY;
      int m_selectionPosX;
      int m_selectionPosY;
      bool m_bActiveSelection = false;
      bool m_bMovingSelection = false;
      Div m_buttonBar;
      Div m_selectionButtons;
      Div m_dbgButtons;
      TMSwitchButton m_btnSelectMode;
      TMButton m_btnSaveSelection;
      TMButton m_btnLoadSelection;
      TMButton m_btnSave;
      TMButton m_btnLoad;
      TMSwitchButton m_btnDebug;
      TMButton m_btnDelete;
      TMButton m_btnStep;
      TMButton m_btnRun;
      TMButton m_btnStop;
      TMButton m_btnMoveTapeFwd;
      TMButton m_btnMoveTapeBack;
      TMButton m_btnChangeTape;
      TMButton m_btnTest;
      TMButton m_btnLeave;
      TMButton[] m_btnChangeColor;
      static int counter = 0;
      private StateCell m_beginSelection;
      private int m_selFromPixelX;
      private int m_selFromPixelY;
      private int m_selToPixelX;
      private int m_selToPixelY;
      jQueryObject m_parent;

      private bool m_bSelectingMode;
      private int m_destSelX;
      private int m_destSelY;
      MessageBoxWithTextArea m_dataDlg;
      Tape[] m_tape;


      bool m_bSelecting = false;
      private bool m_bLoadMode;
      private Tape m_currentTape;
      private bool m_bDebug;
      private bool m_bRunning;
      private int m_iTapeNum;
      private bool m_bTapeMoving;
      private int m_iDelayBetweenSteps = 333;
      Slider m_slider;
      Label m_lblSpeed;
      Label m_lblTestResult;
      Label m_lblDescription;
      private Label m_speedText;
      private bool m_bLoadBlockMode;
      private bool m_bDontRemoveSelectionOnMove;
      Challenge m_task;
      private int m_timeoutRemoveResult;
      private InfoBox m_msgTesting;

      public StateCell pSelectedCell
      {
         get { return m_selectedCell; }
      }

      public StateCell GetCell(int x, int y)
      {
         return cells[y][x];
      }


      public jQueryObject pParent
      {
         get { return m_parent; }
         set { m_parent = value; }
      }

      string[] m_colors = new string[] { "FFFFA7", "E0B2FF", "D1EBFF", "E7F7CA", "FFCDC6", "D8D8D8" };

      public StateBoard(jQueryObject parent, Challenge task, Upgrades abilities)
      {
         m_parent = parent;

         m_lblDescription = new Label(parent, "lblDescription");
         m_lblDescription.pObject.CSS("width", (Math.Max(15, BOARD_SIZE_X) * DISTANCE_CELL) + "px");
         m_lblDescription.pObject.CSS("height", "40px");
         m_lblDescription.pObject.CSS("border", "1px solid black");
         m_lblDescription.pObject.CSS("background-color", "#FFFF80");
         m_lblDescription.pObject.CSS("text-align", "left");
         m_lblDescription.pObject.CSS("padding-left", "10px");
         m_lblDescription.pObject.CSS("padding-right", "10px");
         m_lblDescription.pObject.CSS("position", "absolute");
         m_lblDescription.pPosY = -50;
         m_lblDescription.pText = task.pDescription;

         m_task = task;
         if (!abilities.pExtendedSize)
         {
            BOARD_SIZE_X = 11;
            BOARD_SIZE_Y = 9;
         }
         counter++;
         m_abilities = abilities;
         string suffix = counter + IdFactory.GetId();
         m_buttonBar = new Div(parent, "bar");
         m_buttonBar.pObject.CSS("width", "400px");
         m_buttonBar.pObject.CSS("height", (DISTANCE_CELL * 12) + "px");
         m_buttonBar.pPosX = Math.Max(15, BOARD_SIZE_X) * DISTANCE_CELL + 20;
         m_buttonBar.pPosY = 0;


         m_btnSelectMode = new TMSwitchButton(m_buttonBar.pObject, "Select", true);
         m_btnSelectMode.pPosX = 0;
         m_buttonBar.pObject.Append("<br/>");
         if (!m_abilities.pSelectionEnabled)
            m_btnSelectMode.pVisible = false;

         m_msgTesting = new InfoBox(m_parent);
         m_msgTesting.pLabelText = "Testing machine, please wait ...";

         m_selectionButtons = new Div(m_buttonBar.pObject, "selbar");
         m_selectionButtons.pObject.CSS("margin-left", "30px");
         m_selectionButtons.pObject.CSS("position", "relative");
         m_selectionButtons.pVisible = false;

         m_btnDelete = new TMButton(m_selectionButtons.pObject, "Delete", true);
         m_btnDelete.pEnabled = false;

         m_btnDelete.OnClick += new ButtonEventHandler(m_btnDelete_OnClick);
         m_btnDelete.pObject.Append("<br/>");

         m_btnSaveSelection = new TMButton(m_selectionButtons.pObject, "Save", true);
         m_btnSaveSelection.pEnabled = false;
         m_btnSaveSelection.OnClick += new ButtonEventHandler(m_btnSaveSelection_OnClick);
         m_btnSaveSelection.pObject.Append("<br/>");

         m_btnLoadSelection = new TMButton(m_selectionButtons.pObject, "Load", true);
         m_btnLoadSelection.OnClick += new ButtonEventHandler(m_btnLoadBlock_OnClick);
         m_btnLoadSelection.pObject.Append("<br/>");

         if (!m_abilities.pSaveLoadSelectionEnabled)
         {
            m_btnSaveSelection.pVisible = false;
            m_btnLoadSelection.pVisible = false;
         }
         m_btnChangeColor = new TMButton[6];
         for (int i = 0; i < 6; i++)
         {
            m_btnChangeColor[i] = new TMButton(m_selectionButtons.pObject, "Color #" + (i + 1), true);
            m_btnChangeColor[i].pObject.Children().CSS("background-color", m_colors[i]);
            m_btnChangeColor[i].pObject.Data("color", i);
            m_btnChangeColor[i].pEnabled = false;
            m_btnChangeColor[i].pVisible = m_abilities.pColorizeEnabled;
            m_btnChangeColor[i].OnClick += delegate(TMButton sender)
            {
               int color = (int)sender.pObject.GetDataValue("color");
               if (m_bActiveSelection)
               {
                  for (int y = m_selFromY; y <= m_selToY; y++)
                     for (int x = m_selFromX; x <= m_selToX; x++)
                     {
                        cells[y][x].pBkgNormalColor = color;
                        m_selection[y - m_selFromY][x - m_selFromX].pBkgNormalColor = color;
                     }
               }
            };
            if (m_abilities.pColorizeEnabled && (i % 2) == 1)
               m_selectionButtons.pObject.Append("<br/>");

         }

         m_btnSave = new TMButton(m_buttonBar.pObject, "Save", true);
         m_btnSave.OnClick += new ButtonEventHandler(m_btnSave_OnClick);
         m_buttonBar.pObject.Append("<br/>");

         m_btnLoad = new TMButton(m_buttonBar.pObject, "Load", true);
         m_btnLoad.OnClick += new ButtonEventHandler(m_btnLoad_OnClick);
         m_buttonBar.pObject.Append("<br/>");

         m_btnDebug = new TMSwitchButton(m_buttonBar.pObject, "Debug", true);
         m_btnDebug.pEnabled = true;
         m_btnDebug.OnClick += new ButtonEventHandler(m_btnDebug_OnClick);
         m_buttonBar.pObject.Append("<br/>");
         if (!m_abilities.pDebugEnabled)
         {
            m_btnDebug.pVisible = false;
         }

         m_dbgButtons = new Div(m_buttonBar.pObject, "selbar");
         m_dbgButtons.pObject.CSS("margin-left", "30px");
         m_dbgButtons.pObject.CSS("position", "relative");
         m_dbgButtons.pVisible = false;

         m_btnRun = new TMButton(m_dbgButtons.pObject, "Run", true);
         m_btnRun.OnClick += new ButtonEventHandler(m_btnRun_OnClick);
         m_btnRun.pObject.CSS("margin-left", "30px");

         m_btnStop = new TMButton(m_dbgButtons.pObject, "Stop", true);
         m_btnStop.OnClick += new ButtonEventHandler(m_btnStop_OnClick);
         m_dbgButtons.pObject.Append("<br/>");

         m_btnStep = new TMButton(m_dbgButtons.pObject, "Step", true);
         m_btnStep.OnClick += new ButtonEventHandler(m_btnStep_OnClick);
         m_btnStep.pObject.CSS("margin-left", "30px");

         m_btnChangeTape = new TMButton(m_dbgButtons.pObject, "&varr; Tape", true);
         m_btnChangeTape.OnClick += new ButtonEventHandler(m_btnChangeTape_OnClick);
         m_dbgButtons.pObject.Append("<br/>");
         if (!m_abilities.pTwoTapesEnabled)
         {
            m_btnChangeTape.pVisible = false;
         }

         m_btnMoveTapeFwd = new TMButton(m_dbgButtons.pObject, "Tape &rarr;", true);
         m_btnMoveTapeFwd.OnClick += new ButtonEventHandler(m_btnMoveTapeFwd_OnClick);
         m_btnMoveTapeFwd.pObject.CSS("margin-left", "30px");

         m_btnMoveTapeBack = new TMButton(m_dbgButtons.pObject, "&larr; Tape", true);
         m_btnMoveTapeBack.OnClick += new ButtonEventHandler(m_btnMoveTapeBack_OnClick);
         m_btnMoveTapeBack.pObject.Append("<br/>");
         m_btnMoveTapeBack.pObject.Append("<br/>");

         m_speedText = new Label(m_dbgButtons.pObject, "speedtxt");
         m_speedText.pObject.CSS("position", "relative");
         m_speedText.pText = "Execution speed";
         m_speedText.pObject.CSS("margin-left", "30px");
         m_speedText.pObject.CSS("margin-right", "10px");

         m_slider = new Slider(m_dbgButtons.pObject, "runspeed", 50, 2050, 200);
         m_slider.pObject.CSS("position", "relative");
         m_slider.OnChange += delegate
         {
            m_lblSpeed.pText = m_slider.pValue.ToString();
            m_iDelayBetweenSteps = m_slider.pValue;
         };
         m_slider.pObject.CSS("margin-right", "10px");
         m_slider.pValue = 850;
         m_iDelayBetweenSteps = m_slider.pValue;
         m_returnPoint = new Stack<StateCell>[MAX_OP];
         m_lastCalled = new StateCell[MAX_OP];
         m_lblSpeed = new Label(m_dbgButtons.pObject, "speed");
         m_lblSpeed.pObject.CSS("position", "relative");
         m_lblSpeed.pText = m_slider.pValue.ToString();

         m_btnTest = new TMButton(m_buttonBar.pObject, "Test", true);
         m_btnTest.OnClick += new ButtonEventHandler(m_btnTest_OnClick);
         m_buttonBar.pObject.Append("<br/>");

         m_btnLeave = new TMButton(m_buttonBar.pObject, "Leave", true);
         m_btnLeave.OnClick += new ButtonEventHandler(m_btnLeave_OnClick);

         for (int i = 0; i < MAX_OP; i++)
         {
            m_returnPoint[i] = new Stack<StateCell>();
            m_lastCalled[i] = null;
         }

         cells = new StateCell[BOARD_SIZE_Y][];
         for (int i = 0; i < BOARD_SIZE_Y; i++)
            cells[i] = new StateCell[BOARD_SIZE_X];
         for (int i = 0; i < BOARD_SIZE_Y; i++)
         {
            for (int j = 0; j < BOARD_SIZE_X; j++)
            {
               cells[i][j] = new StateCell(parent);
               cells[i][j].pBkgNormalColor = 3;
               cells[i][j].pPosX = j * DISTANCE_CELL;
               cells[i][j].pPosY = i * DISTANCE_CELL;
               cells[i][j].pOperation = -1;
               cells[i][j].pCharacter = -1;
               cells[i][j].pAltMovement = -1;
               cells[i][j].pMovement = -1;
               cells[i][j].pObject.Data("x", j);
               cells[i][j].pObject.Data("y", i);

               cells[i][j].OnMouseEnter += delegate(StateCell cell, jQueryEvent ev)
               {
                  if (!m_editor.pDropDown)
                     cell.pbHovered = true;
               };

               cells[i][j].OnMouseLeave += delegate(StateCell cell, jQueryEvent ev)
               {
                  if (!m_editor.pDropDown)
                     cell.pbHovered = false;
               };
               cells[i][j].OnClick += new CellEventHandler(StateBoard_OnClick);
            }
         }
         m_editor = new CellEditor(this, "selector");
         m_editor.VisibilityChanges += new EventHandler(m_editor_VisibilityChanges);

         m_frameSelector = new Div(parent, "frameSelection");
         m_frameSelector.pObject.AddClass("curractive");
         m_frameSelector.pObject.CSS("display", "none");
         m_btnSelectMode.OnClick += new ButtonEventHandler(m_btnSelectMode_OnClick);

         m_selectionMirror = new Div(parent, "selectionCopy");
         m_selectionMirror.pObject.AddClass("curractive");
         m_selectionMirror.pObject.CSS("display", "none");
         m_selectionMirror.pObject.CSS("z-Index", "18");
         m_selectionMirror.pObject.CSS("opacity", "0.7");

         m_dataDlg = new MessageBoxWithTextArea(parent.Parent());

         m_movementBackground = new Div(parent.Parent(), "movementBackground");
         m_movementBackground.pObject.CSS("display", "none");
         m_movementBackground.pObject.CSS("opacity", "0");
         m_movementBackground.pPosX = 0;
         m_movementBackground.pPosY = 0;
         m_movementBackground.pObject.CSS("z-Index", "30");
         m_movementBackground.pObject.CSS("background-color", "Red");

         m_movementBackground.pObject.MouseDown(BoardMouseDown);

         m_movementBackground.pObject.MouseUp(BoardMouseUp);

         m_movementBackground.pObject.MouseMove(BoardMouseMove);

         m_dataDlg.OnClose += new DialogEventHandler(m_dataDlg_OnClose);

         m_tape = new Tape[2];
         m_tape[0] = new Tape(this, "tapered");
         m_tape[0].pPosX = 0;
         m_tape[0].pPosY = (BOARD_SIZE_Y + 1) * DISTANCE_CELL;
         m_tape[0].SetTorchVisible(false);

         m_tape[1] = new Tape(this, "tapewhite");
         m_tape[1].pPosX = 0;
         m_tape[1].pPosY = (BOARD_SIZE_Y + 1) * DISTANCE_CELL + 40;
         m_tape[1].SetTorchVisible(false);

         m_tape[0].pReadOnly = true;
         m_tape[1].pReadOnly = true;

         SetCurrentTape(1);

         if (!m_abilities.pTwoTapesEnabled)
            m_tape[0].pObject.Hide();

         OnDebugEnd += new MessageHandler(StateBoard_OnDebugEnd);
         m_lblTestResult = new Label(parent, "lblTestResult");
         m_lblTestResult.pVisible = false;
         m_lblTestResult.pObject.CSS("width", "300px");
         m_lblTestResult.pObject.CSS("height", "100px");
         m_lblTestResult.pObject.CSS("border", "2px solid black");
         m_lblTestResult.pObject.CSS("background-color", "#FF8080");
         m_lblTestResult.pObject.CSS("text-align", "center");
         m_lblTestResult.pObject.CSS("padding", "20px 10px 2px 10px");
         m_lblTestResult.pObject.CSS("vertical-align", "bottom");

         m_lblTestResult.pPosY = m_tape[1].pPosY + 10;
         m_lblTestResult.pPosX = 600;

         string machine = Window.LocalStorage.GetItem("TM_Level_" + task.pName) as string;
         if (machine != null)
            LoadBoardFromString(machine);
      }

      void m_btnLeave_OnClick(TMButton sender)
      {
         if (OnLevelAbandoned != null)
         {
            SaveLevel();
            OnLevelAbandoned(this, null);
         }
      }

      private void SaveLevel()
      {
         Window.LocalStorage["TM_Level_" + m_task.pName] = GetBoardAsString();
      }


      void m_btnTest_OnClick(TMButton sender)
      {
         m_btnTest.pEnabled = false;
         if (m_bDebug) SwitchDebug();
         if (m_timeoutRemoveResult != -1)
         {
            Window.ClearTimeout(m_timeoutRemoveResult);
            m_timeoutRemoveResult = -1;
            m_lblTestResult.pVisible = false;
            OnDebugEnd -= StateBoard_OnShowTestEnd;
         }
         m_msgTesting.Show();

         Window.SetTimeout(delegate
         {

            bool bAllPassed = true;
            foreach (ChallengeTest test in m_task.pTests)
            {
               string testResult = null;
               m_tape[0].Clear();
               m_tape[1].Clear();
               m_tape[1].pTapeValue = test.pInputString;
               SetCurrentTape(1);
               pExecutionPoint = cells[0][0];
               m_bRunning = true;
               string result = RunStep(30000, false);
               bool bPassed = true;
               if (result != null)
               {
                  testResult = result + " with the input string '" + test.pInputString + "'";
                  bPassed = false;
               }
               else if (test.pExpectedAcceptance == AcceptState.Accept && pExecutionPoint.pOperation != OP_ACCEPT)
               {
                  testResult = "This machine rejects the string '" + test.pInputString + "' which should be accepted";
                  bPassed = false;
               }
               else if (test.pExpectedAcceptance == AcceptState.Reject && pExecutionPoint.pOperation != -1)
               {
                  testResult = "This machine accepts the string '" + test.pInputString + "' which should be rejected";
                  bPassed = false;
               }
               else if (test.pExpectedOutputString != null && test.pExpectedOutputString != m_tape[1].pTapeValue)
               {
                  testResult = "This machine generates '" + m_tape[1].pTapeValue + "' for the input '" + test.pInputString + "' and the expected result is '" + test.pExpectedOutputString + "'";
                  bPassed = false;
               }

               if (!bPassed)
               {
                  m_msgTesting.Close(CloseResult.none);
                  bAllPassed = false;
                  m_tape[0].Clear();
                  m_tape[1].Clear();
                  SetCurrentTape(1);
                  m_tape[1].pTapeValue = test.pInputString;
                  pExecutionPoint = cells[0][0];
                  m_bRunning = true;
                  SwitchDebug();
                  m_tape[0].Refresh();
                  m_tape[1].Refresh();
                  OnDebugEnd += new MessageHandler(StateBoard_OnShowTestEnd);
                  m_lblTestResult.pText = testResult;
                  m_lblTestResult.pObject.CSS("display", "table-cell");
                  m_btnRun_OnClick(m_btnRun);
                  break;
               }
            }

            if (bAllPassed)
            {
               ChallengeTest test = m_task.pTests[m_task.pTests.Count - 1];
               m_msgTesting.Close(CloseResult.none);
               bAllPassed = false;
               m_tape[0].Clear();
               m_tape[1].Clear();
               SetCurrentTape(1);
               m_tape[1].pTapeValue = test.pInputString;
               pExecutionPoint = cells[0][0];
               m_bRunning = true;
               SwitchDebug();
               m_tape[0].Refresh();
               m_tape[1].Refresh();
               OnDebugEnd += new MessageHandler(showSuccessDebugEnd);
               m_lblTestResult.pObject.CSS("background-color", "#80FF80");
               m_lblTestResult.pText = "Your machine works !. This is the test for the input string " + test.pInputString;
               m_lblTestResult.pObject.CSS("display", "table-cell");
               m_btnRun_OnClick(m_btnRun);
            }
         }, 1000);
      }

      void showSuccessDebugEnd(object sender, string msg)
      {
         m_btnTest.pEnabled = true;
         Script.Alert("Congratulations! Your machine passed all the tests. You will be taken to the main menu.");
         if (OnLevelCompleted != null)
         {
            SaveLevel();
            OnLevelCompleted(this, null);
         }
      }

      void m_btnStop_OnClick(TMButton sender)
      {
         m_bRunning = false;
      }

      void StateBoard_OnShowTestEnd(object sender, string msg)
      {
         m_btnTest.pEnabled = true;
         OnDebugEnd -= StateBoard_OnShowTestEnd;
         m_timeoutRemoveResult = Window.SetTimeout(delegate
         {
            m_timeoutRemoveResult = -1;
            m_lblTestResult.pObject.FadeOut(2000);

         }, 10000);
      }

      #region debugging

      void m_btnStep_OnClick(TMButton sender)
      {
         Step(true, null);
      }

      void m_btnRun_OnClick(TMButton sender)
      {
         m_bRunning = true;
         SetDebugButtonsEnability();
         RunStep(30000, true);
      }

      string RunStep(int count, bool bBreakEnabled)
      {
         if (count == 0)
         {
            if (OnDebugEnd != null)
               OnDebugEnd(this, "Infinite loop");
         }
         else
         {
            if (!bBreakEnabled)
            {
               string message = null;
               while (m_bRunning && count > 0)
               {
                  StateCell currSell = pExecutionPoint;
                  Step(true, delegate { });
                  if (pExecutionPoint == currSell)
                     break;
                  count--;
                  if ((count % 500) == 0 &&
                     (m_tape[0].pCount > 1000 || m_tape[1].pCount > 500))
                  {
                     message = "Data overflow";
                     break;
                  }
                  if (count <= 0)
                     message = "Infinite loop";
               }
               return message;
            }
            else
            {
               if (m_bRunning)
               {
                  StateCell currSell = pExecutionPoint;
                  Step(true, delegate
                  {
                     if (pExecutionPoint != currSell)
                     {
                        Window.SetTimeout(delegate
                        {
                           RunStep(count - 1, bBreakEnabled);
                        }, m_iDelayBetweenSteps);
                     }
                     else
                     {
                        if (OnDebugEnd != null)
                           OnDebugEnd(this, null);
                     }
                  });
               }
               else
                  if (OnDebugEnd != null)
                     OnDebugEnd(this, null);
            }
         }
         return null;
      }


      void StateBoard_OnDebugEnd(object sender, string message)
      {
         m_bRunning = false;
         SetDebugButtonsEnability();
      }


      private void SetCurrentTape(int i4TapeNum)
      {
         m_tape[0].SetTorchVisible(i4TapeNum == 0);
         m_tape[1].SetTorchVisible(i4TapeNum == 1);
         m_currentTape = m_tape[i4TapeNum];
         m_iTapeNum = i4TapeNum;
         if (i4TapeNum == 0)
            m_tape[0].pPosY = (BOARD_SIZE_Y + 1) * DISTANCE_CELL;
         else
            m_tape[0].pPosY = (BOARD_SIZE_Y + 1) * DISTANCE_CELL - 20;

         m_tape[1].pPosY = (BOARD_SIZE_Y + 1) * DISTANCE_CELL + 35;
      }

      void m_btnDebug_OnClick(TMButton sender)
      {
         if (m_btnSelectMode.pPressed)
            SwitchSelectMode();
         SwitchDebug();
      }

      private void SwitchDebug()
      {
         m_bDebug = !m_bDebug;
         m_btnDebug.pPressed = m_bDebug;
         m_tape[0].pDebugMode = m_bDebug;
         m_tape[1].pDebugMode = m_bDebug;
         if (m_bDebug)
         {
            m_dbgButtons.pObject.SlideDown();
            SetDebugButtonsEnability();
            if (pExecutionPoint == null)
               pExecutionPoint = cells[0][0];
         }
         else
         {
            m_dbgButtons.pObject.SlideUp();
         }
         m_tape[0].pReadOnly = !m_bDebug;
         m_tape[1].pReadOnly = !m_bDebug;
         pExecutionPoint.SetThickBorder(m_bDebug);
      }

      private void SetDebugButtonsEnability()
      {
         m_btnChangeTape.pEnabled = !m_bRunning && !m_bTapeMoving;
         m_btnMoveTapeBack.pEnabled = !m_bRunning && !m_bTapeMoving;
         m_btnMoveTapeFwd.pEnabled = !m_bRunning && !m_bTapeMoving;
         m_btnStep.pEnabled = !m_bRunning && !m_bTapeMoving && m_abilities.pStepEnabled;
         m_btnStop.pEnabled = m_bRunning;
         m_btnRun.pEnabled = !m_bRunning && !m_bTapeMoving;

         m_tape[0].pReadOnly = m_bRunning || m_bTapeMoving;
         m_tape[1].pReadOnly = m_bRunning || m_bTapeMoving;

         if (!m_btnDebug.pVisible && m_bDebug && !m_bRunning)  // Hide debugger if not enabled
            SwitchDebug();
      }

      void m_btnChangeTape_OnClick(TMButton sender)
      {
         SetCurrentTape(1 - m_iTapeNum);
      }

      void m_btnMoveTapeFwd_OnClick(TMButton sender)
      {
         m_bTapeMoving = true;
         SetDebugButtonsEnability();
         m_currentTape.Move(1, 0, delegate
         {
            m_bTapeMoving = false;
            SetDebugButtonsEnability();
         });
      }

      void m_btnMoveTapeBack_OnClick(TMButton sender)
      {
         m_bTapeMoving = true;
         SetDebugButtonsEnability();
         m_currentTape.Move(-1, 0, delegate
         {
            m_bTapeMoving = false;
            SetDebugButtonsEnability();
         });
      }

      public StateCell pExecutionPoint
      {
         get
         {
            return m_executionPoint;
         }
         set
         {
            if (m_executionPoint != value)
            {
               if (m_executionPoint != null)
                  m_executionPoint.SetThickBorder(false);
               m_executionPoint = value;
               if (m_bDebug)
                  m_executionPoint.SetThickBorder(true);
            }
         }
      }

      public void Step(bool bSlow, EventHandler onCompletion)
      {
         bool bStandardMovement = true;
         switch (pExecutionPoint.pOperation)
         {
            case OP_COMPARE_CONST:
               {
                  int currChar = m_currentTape.ReadValue();
                  int checkChar = pExecutionPoint.pCharacter;
                  if (currChar == checkChar)
                     DoMovement(pExecutionPoint.pAltMovement);
                  else
                     DoMovement(pExecutionPoint.pMovement);
                  if (onCompletion != null)
                     onCompletion(this, null);
                  bStandardMovement = false;
                  break;
               }
            case OP_WRITE_CONST:
               {
                  m_currentTape.WriteValue(pExecutionPoint.pCharacter);
                  break;
               }
            case OP_TAPE_FWD:
            case OP_TAPE_BACK:
               {
                  m_bTapeMoving = true;
                  SetDebugButtonsEnability();
                  m_currentTape.Move(pExecutionPoint.pOperation == 2 ? 1 : -1, bSlow ? 0 : 2, delegate
                  {
                     DoMovement(pExecutionPoint.pMovement);
                     m_bTapeMoving = false;
                     SetDebugButtonsEnability();
                     if (onCompletion != null)
                        onCompletion(this, null);
                  });
                  bStandardMovement = false;
                  break;
               }
            case OP_WHITE_TAPE:
            case OP_RED_TAPE:
               {
                  SetCurrentTape(pExecutionPoint.pOperation - 4);
                  break;
               }
            case OP_COMPARE_TAPES:
               {
                  int val1 = m_tape[0].ReadValue();
                  int val2 = m_tape[1].ReadValue();
                  if (val1 == val2)
                     DoMovement(pExecutionPoint.pAltMovement);
                  else
                     DoMovement(pExecutionPoint.pMovement);
                  if (onCompletion != null)
                     onCompletion(this, null);
                  bStandardMovement = false;
               }
               break;
            case OP_ACCEPT:
               m_bRunning = false;
               break;
            case OP_FLIP_BIT:
               {
                  int val1 = m_currentTape.ReadValue();
                  if (val1 == 0 || val1 == 1)
                  {
                     val1 = 1 - val1;
                     m_currentTape.WriteValue(val1);
                  }
               }
               break;
            case OP_WRITE_VAL_TAPE:
               {
                  int val1 = m_tape[1 - m_iTapeNum].ReadValue();
                  m_currentTape.WriteValue(val1);
               }
               break;
            case OP_FUNCTION:
               {
                  if (pExecutionPoint.pMovement == -1)
                  {
                     if (m_returnPoint[pExecutionPoint.pCharacter].Count > 0)
                        pExecutionPoint = m_returnPoint[pExecutionPoint.pCharacter].Pop();
                  }
                  else
                  {
                     if (m_returnPoint[pExecutionPoint.pCharacter].Count > 0)
                     {
                        // Already called, use same function again
                        if (m_lastCalled[pExecutionPoint.pCharacter] != null)
                        {
                           m_returnPoint[pExecutionPoint.pCharacter].Push(pExecutionPoint);
                           pExecutionPoint = m_lastCalled[pExecutionPoint.pCharacter];
                        }
                     }
                     else
                     {
                        // Not called, search for a function node
                        bool bFound = false;
                        for (int i = 0; i < BOARD_SIZE_Y; i++)
                        {
                           for (int j = 0; j < BOARD_SIZE_X; j++)
                              if (cells[i][j].pOperation == 10 &&
                                 cells[i][j].pCharacter == pExecutionPoint.pCharacter &&
                                 cells[i][j].pMovement != -1 &&
                                 cells[i][j] != pExecutionPoint)
                              {
                                 bool bPointedByOther = false;
                                 // Only if no other cell is pointing this cell
                                 for (int dy = -1; dy < 2; dy++)
                                    for (int dx = -1; dx < 2; dx++)
                                    {
                                       if (dy + i > 0 && dy + i < BOARD_SIZE_Y && dx + j > 0 && dx + j < BOARD_SIZE_X &&
                                          GetPointedCell(cells[dy + i][dx + j], cells[dy + i][dx + j].pMovement) == cells[i][j])
                                       {
                                          bPointedByOther = true;
                                          break;
                                       }
                                    }

                                 if (!bPointedByOther)
                                 {
                                    m_returnPoint[pExecutionPoint.pCharacter].Push(pExecutionPoint);
                                    pExecutionPoint = cells[i][j];
                                    m_lastCalled[pExecutionPoint.pCharacter] = pExecutionPoint;
                                    break;
                                 }
                              }
                           if (bFound) break;
                        }
                     }

                  }
               }
               break;
            case OP_INSERTCELL:
               m_currentTape.InsertCell(bSlow ? 0 : 2, delegate
               {
                  DoMovement(pExecutionPoint.pMovement);
                  m_bTapeMoving = false;
                  SetDebugButtonsEnability();
                  if (onCompletion != null)
                     onCompletion(this, null);
               });
               bStandardMovement = false;
               break;
            case OP_REMOVECELL:
               m_currentTape.RemoveCell(bSlow ? 0 : 2, delegate
               {
                  DoMovement(pExecutionPoint.pMovement);
                  m_bTapeMoving = false;
                  SetDebugButtonsEnability();
                  if (onCompletion != null)
                     onCompletion(this, null);
               });
               bStandardMovement = false;
               break;
         }
         if (bStandardMovement)
         {
            DoMovement(pExecutionPoint.pMovement);
            if (onCompletion != null)
               onCompletion(this, null);
         }
      }

      private void DoMovement(int dir)
      {
         pExecutionPoint = GetPointedCell(pExecutionPoint, dir);
      }

      private StateCell GetPointedCell(StateCell cell, int dir)
      {
         int toX = (int)cell.pObject.GetDataValue("x");
         int toY = (int)cell.pObject.GetDataValue("y");

         int deltaX = 0, deltaY = 0;
         switch (dir % 8)
         {
            case 0:
               deltaX = 0; deltaY = -1;
               break;
            case 1:
               deltaX = 1; deltaY = -1;
               break;
            case 2:
               deltaX = 1; deltaY = 0;
               break;
            case 3:
               deltaX = 1; deltaY = 1;
               break;
            case 4:
               deltaX = 0; deltaY = 1;
               break;
            case 5:
               deltaX = -1; deltaY = 1;
               break;
            case 6:
               deltaX = -1; deltaY = 0;
               break;
            case 7:
               deltaX = -1; deltaY = -1;
               break;
         }
         if (dir > 7)
         {
            deltaX *= 2;
            deltaY *= 2;
         }
         toX += deltaX;
         toY += deltaY;
         if (toX < 0) toX = BOARD_SIZE_X-1;
         if (toY < 0) toY = BOARD_SIZE_Y-1;
         if (toX >= BOARD_SIZE_X) toX = 0;
         if (toY >= BOARD_SIZE_Y) toY = 0;
         return cells[toY][toX];
      }
      #endregion

      void m_dataDlg_OnClose(MessageBoxBase dlg, bool bOkPressed)
      {
         if (bOkPressed)
         {
            if (m_bLoadMode)
               LoadBoardFromString(m_dataDlg.pEditText);
            if (m_bLoadBlockMode)
               LoadBlockFromString(m_dataDlg.pEditText);

            m_bLoadBlockMode = false;
            m_bLoadMode = false;
         }
      }


      private int GetOffsetX(jQueryEvent ev)
      {
         return ev.PageX - pParent.Position().Left;
      }


      private int GetOffsetY(jQueryEvent ev)
      {
         return ev.PageY - pParent.Position().Top;
      }

      #region selection handlers
      void m_btnSelectMode_OnClick(TMButton sender)
      {
         if (m_bDebug)
            SwitchDebug();
         SwitchSelectMode();
      }

      private void SwitchSelectMode()
      {
         m_bSelectingMode = !m_bSelectingMode;
         m_btnSelectMode.pPressed = m_bSelectingMode;
         if (m_bSelectingMode)
            m_selectionButtons.pObject.SlideDown();
         else
            m_selectionButtons.pObject.SlideUp();
         m_btnDelete.pEnabled = false;
         for (int clBtn = 0; clBtn < 6; clBtn++)
            m_btnChangeColor[clBtn].pEnabled = m_btnDelete.pEnabled;
         m_btnSaveSelection.pEnabled = false;
         if (m_bSelectingMode)
         {
            m_movementBackground.pObject.Show();
            m_movementBackground.pObject.CSS("width", (DISTANCE_CELL * BOARD_SIZE_X) + "px");
            m_movementBackground.pObject.CSS("height", (DISTANCE_CELL * BOARD_SIZE_Y) + "px");
            m_movementBackground.pPosX = pParent.Position().Left;
            m_movementBackground.pPosY = pParent.Position().Top;
         }
         else
         {
            m_bActiveSelection = false;
            UpdateSelectionFrame();
            m_selFromX = m_selToX = -1;
            m_selFromPixelX = m_selToPixelX = -1;
            m_selectionMirror.pObject.Hide();
            m_frameSelector.pObject.Hide();
            m_movementBackground.pObject.Hide();
         }
      }


      private void HandleSelectionMove(jQueryEvent ev)
      {
         int x = GetOffsetX(ev);
         int y = GetOffsetY(ev);
         int deltaX = x - m_pinSelectionX;
         int deltaY = y - m_pinSelectionY;
         m_selectionPosX += deltaX;
         m_selectionPosY += deltaY;
         m_pinSelectionX = x;
         m_pinSelectionY = y;
         m_destSelY = (int)(m_selectionPosY / DISTANCE_CELL);
         m_destSelX = (int)(m_selectionPosX / DISTANCE_CELL);
         if (m_destSelX < 0) m_destSelX = 0;
         if (m_destSelY < 0) m_destSelY = 0;
         if (m_destSelX + m_selToX - m_selFromX >= BOARD_SIZE_X)
            m_destSelX = BOARD_SIZE_X - (m_selToX - m_selFromX) - 1;
         if (m_destSelY + m_selToY - m_selFromY >= BOARD_SIZE_Y)
            m_destSelY = BOARD_SIZE_Y - (m_selToY - m_selFromY) - 1;
         m_selectionMirror.pPosY = m_destSelY * DISTANCE_CELL;
         m_selectionMirror.pPosX = m_destSelX * DISTANCE_CELL;
         if (ev.CtrlKey)
            m_movementBackground.pObject.CSS("cursor", "copy");
         else
            m_movementBackground.pObject.CSS("cursor", "move");
      }


      private void BoardMouseMove(jQueryEvent ev)
      {
         int x = GetOffsetX(ev);
         int y = GetOffsetY(ev);
         if (m_bMovingSelection)
            HandleSelectionMove(ev);
         else if (m_bSelecting)
         {
            int cx = (int)(x / DISTANCE_CELL);
            int cy = (int)(y / DISTANCE_CELL);
            if (cx < 0) cx = 0;
            if (cy < 0) cy = 0;
            if (cx > BOARD_SIZE_X - 1) cy = BOARD_SIZE_X - 1;
            if (cy > BOARD_SIZE_Y - 1) cy = BOARD_SIZE_Y - 1;
            UpdateSelection(cells[cy][cx]);
         }
         else
         {
            if (x >= m_selFromPixelX && x <= m_selToPixelX &&
               y >= m_selFromPixelY && y <= m_selToPixelY)
            {
               if (ev.CtrlKey)
                  m_movementBackground.pObject.CSS("cursor", "copy");
               else
                  m_movementBackground.pObject.CSS("cursor", "move");
            }
            else
               m_movementBackground.pObject.CSS("cursor", "auto");
         }
      }

      private void BoardMouseUp(jQueryEvent ev)
      {
         if (m_bSelecting)
         {
            int x = GetOffsetX(ev);
            int y = GetOffsetY(ev);
            int cx = (int)(x / DISTANCE_CELL);
            int cy = (int)(y / DISTANCE_CELL);
            if (cx < 0) cx = 0;
            if (cy < 0) cy = 0;
            if (cx > BOARD_SIZE_X - 1) cy = BOARD_SIZE_X - 1;
            if (cy > BOARD_SIZE_Y - 1) cy = BOARD_SIZE_Y - 1;
            EndSelection(cells[cy][cx]);
            m_btnDelete.pEnabled = true;
            for (int clBtn = 0; clBtn < 6; clBtn++)
               m_btnChangeColor[clBtn].pEnabled = m_btnDelete.pEnabled;
            m_btnSaveSelection.pEnabled = true;
         }
         else if (m_bMovingSelection)
         {
            m_bMovingSelection = false;
            m_movementBackground.pObject.CSS("cursor", "auto");
            int destFromX = m_destSelX;
            int destFromY = m_destSelY;
            int destToX = m_destSelX + m_selToX - m_selFromX;
            int destToY = m_destSelY + m_selToY - m_selFromY;

            CopySelectionTo(destFromX, destFromY, !ev.CtrlKey);
            m_selToX = destToX;
            m_selToY = destToY;
            m_selFromX = destFromX;
            m_selFromY = destFromY;

            m_frameSelector.pObject.CSS("top", m_destSelY * DISTANCE_CELL + "px");
            m_frameSelector.pObject.CSS("left", m_destSelX * DISTANCE_CELL + "px");

            UpdateSelectionFrame();
            CalcPixelPosOfSelection();

         }
         m_movementBackground.pPosX = pParent.Position().Left;
         m_movementBackground.pPosY = pParent.Position().Top;
         m_movementBackground.pObject.CSS("width", (DISTANCE_CELL * BOARD_SIZE_X) + "px");
         m_movementBackground.pObject.CSS("height", (DISTANCE_CELL * BOARD_SIZE_Y) + "px");
      }

      private void BoardMouseDown(jQueryEvent ev)
      {
         ev.PreventDefault();
         int x = GetOffsetX(ev);
         int y = GetOffsetY(ev);

         if (x >= m_selFromPixelX && x <= m_selToPixelX &&
            y >= m_selFromPixelY && y <= m_selToPixelY)
         {
            m_bMovingSelection = true;
            m_movementBackground.pObject.CSS("cursor", "move");
            m_pinSelectionX = x;
            m_pinSelectionY = y;
            m_destSelX = m_selFromX;
            m_destSelY = m_selFromY;
            m_selectionPosX = m_selectionMirror.pPosX;
            m_selectionPosY = m_selectionMirror.pPosY;
         }
         else
         {
            int cx = (int)(x / DISTANCE_CELL);
            int cy = (int)(y / DISTANCE_CELL);
            BeginSelection(cells[cy][cx]);
            m_bSelecting = true;
            m_bActiveSelection = false;
            m_bMovingSelection = false;
            m_selectionMirror.pObject.Hide();
            UpdateSelectionFrame();
         }
         m_movementBackground.pPosX = 0;
         m_movementBackground.pPosY = 0;
         m_movementBackground.pObject.CSS("width", (Document.DocumentElement.ClientWidth) + "px");
         m_movementBackground.pObject.CSS("height", (Window.InnerHeight) + "px");
      }


      void m_btnSaveSelection_OnClick(TMButton sender)
      {
         m_bLoadMode = false;
         m_dataDlg.pLabelText = "This string contains the code of the current selection. You can paste it again later to restore the selected part";
         m_dataDlg.pEditText = GetPartAsString(m_selFromX, m_selFromY, m_selToX, m_selToY);
         m_dataDlg.Show();
      }


      void m_btnDelete_OnClick(TMButton sender)
      {
         int selWidth = m_selToX - m_selFromX + 1;
         int selHeight = m_selToY - m_selFromY + 1;
         for (int x = 0; x < selWidth; x++)
            for (int y = 0; y < selHeight; y++)
            {
               cells[y + m_selFromY][x + m_selFromX].pOperation = -1;
               cells[y + m_selFromY][x + m_selFromX].pMovement = -1;
               cells[y + m_selFromY][x + m_selFromX].pAltMovement = -1;
               cells[y + m_selFromY][x + m_selFromX].pCharacter = -1;
               cells[y + m_selFromY][x + m_selFromX].pBkgNormalColor = 3;
            }
         m_btnDelete.pEnabled = false;
         for (int clBtn = 0; clBtn < 6; clBtn++)
            m_btnChangeColor[clBtn].pEnabled = m_btnDelete.pEnabled;
         m_btnSaveSelection.pEnabled = false;
         m_selectionMirror.pObject.Hide();
         m_frameSelector.pObject.Hide();
         m_bActiveSelection = false;
         m_bMovingSelection = false;
         m_selFromPixelX = -1;
         m_selToPixelX = -1;
         UpdateSelectionFrame();
      }

      private void CopySelectionTo(int destFromX, int destFromY, bool bMove)
      {
         int selWidth = m_selToX - m_selFromX + 1;
         int selHeight = m_selToY - m_selFromY + 1;

         if (bMove && !m_bDontRemoveSelectionOnMove)
            for (int x = 0; x < selWidth; x++)
               for (int y = 0; y < selHeight; y++)
               {
                  cells[y + m_selFromY][x + m_selFromX].pOperation = -1;
                  cells[y + m_selFromY][x + m_selFromX].pMovement = -1;
                  cells[y + m_selFromY][x + m_selFromX].pCharacter = -1;
                  cells[y + m_selFromY][x + m_selFromX].pAltMovement = -1;
                  cells[y + m_selFromY][x + m_selFromX].pBkgNormalColor = 3;
               }

         for (int y = 0; y < selHeight; y++)
         {
            for (int x = 0; x < selWidth; x++)
            {
               cells[y + destFromY][x + destFromX].GetDataFrom(m_selection[y][x]);
            }
         }
         m_bDontRemoveSelectionOnMove = false;
      }


      private void BeginSelection(StateCell cell)
      {
         m_bActiveSelection = false;
         m_bSelecting = true;
         UpdateSelectionFrame();
         m_beginSelection = cell;
         UpdateSelection(cell);
      }

      private void EndSelection(StateCell cell)
      {
         UpdateSelection(cell);
         m_bSelecting = false;
         m_bActiveSelection = true;
         UpdateSelectionFrame();
         m_selectionMirror.pObject.Empty();
         m_selection = new StateCell[m_selToY - m_selFromY + 1][];
         for (int y = m_selFromY; y < m_selToY + 1; y++)
         {
            m_selection[y - m_selFromY] = new StateCell[m_selToX - m_selFromX + 1];
            for (int x = m_selFromX; x < m_selToX + 1; x++)
            {
               StateCell copiedCell = new StateCell(m_selectionMirror.pObject);
               copiedCell.pbHighlighted = false;
               copiedCell.pPosX = (x - m_selFromX) * DISTANCE_CELL;
               copiedCell.pPosY = (y - m_selFromY) * DISTANCE_CELL;
               copiedCell.GetDataFrom(cells[y][x]);
               m_selection[y - m_selFromY][x - m_selFromX] = copiedCell;
            }
         }
         m_selectionMirror.pPosY = m_selFromY * DISTANCE_CELL;
         m_selectionMirror.pPosX = m_selFromX * DISTANCE_CELL;
         m_selectionMirror.pObject.CSS("width", ((((m_selToX - m_selFromX) + 1) * DISTANCE_CELL) - 2) + "px");
         m_selectionMirror.pObject.CSS("height", ((((m_selToY - m_selFromY) + 1) * DISTANCE_CELL) - 2) + "px");
         m_selectionMirror.pObject.Show();
         m_movementBackground.pObject.CSS("width", Document.DocumentElement.ClientWidth + "px");
         m_movementBackground.pObject.CSS("height", Window.InnerHeight + "px");
         CalcPixelPosOfSelection();
         m_bDontRemoveSelectionOnMove = false;
      }


      private void UpdateSelectionFrame()
      {
         for (int i = 0; i < BOARD_SIZE_Y; i++)
            for (int j = 0; j < BOARD_SIZE_X; j++)
            {
               if (m_bActiveSelection &&
                  i >= m_selFromY && i <= m_selToY &&
                  j >= m_selFromX && j <= m_selToX)
               {
                  cells[i][j].pbHighlighted = true;
               }
               else
               {
                  cells[i][j].pbHighlighted = false;
               }
            }
      }

      private void UpdateSelection(StateCell cell)
      {
         m_frameSelector.pObject.Show();
         int toX = (int)cell.pObject.GetDataValue("x");
         int toY = (int)cell.pObject.GetDataValue("y");
         int fromX = (int)m_beginSelection.pObject.GetDataValue("x");
         int fromY = (int)m_beginSelection.pObject.GetDataValue("y");
         m_selFromX = Math.Min(toX, fromX);
         m_selFromY = Math.Min(toY, fromY);
         m_selToX = Math.Max(toX, fromX);
         m_selToY = Math.Max(toY, fromY);
         m_frameSelector.pPosY = m_selFromY * DISTANCE_CELL;
         m_frameSelector.pPosX = m_selFromX * DISTANCE_CELL;
         m_frameSelector.pObject.CSS("height", ((m_selToY - m_selFromY + 1) * DISTANCE_CELL - 2).ToString() + "px");
         m_frameSelector.pObject.CSS("width", ((m_selToX - m_selFromX + 1) * DISTANCE_CELL - 2).ToString() + "px");
      }

      private void CalcPixelPosOfSelection()
      {
         m_selFromPixelX = m_selFromX * DISTANCE_CELL;
         m_selFromPixelY = m_selFromY * DISTANCE_CELL;
         m_selToPixelX = (m_selToX + 1) * DISTANCE_CELL;
         m_selToPixelY = (m_selToY + 1) * DISTANCE_CELL;
      }


      public bool pSelectionMode
      {
         get { return m_bSelectingMode; }
         set { m_bSelectingMode = value; }
      }
      #endregion

      void StateBoard_OnClick(StateCell cell, jQueryEvent ev)
      {
         if (m_bDebug)
         {
            if (!m_bRunning)
               pExecutionPoint = cell;
         }
         else if (!m_bSelectingMode && !m_editor.pDropDown && !m_bDebug)
         {
            m_selectedCell = cell;
            m_editor.EditCell(m_selectedCell);
         }
      }


      void m_editor_VisibilityChanges(object sender, EventArgs e)
      {
         m_btnDebug.pEnabled = !m_editor.pDropDown;
         m_btnSelectMode.pEnabled = !m_editor.pDropDown;
      }


      #region save/load
      private void LoadBoardFromString(string inputString)
      {
         if (inputString.StartsWith("?") || inputString.StartsWith("!"))
            LoadBoardFromStringNewMethod(inputString);
         else
         {
            inputString = inputString.Replace("=", "--");
            inputString = inputString.Replace("-", "__");
            inputString = inputString.Replace("_", "::");
            inputString = inputString.Replace(":", "..");

            string copy = inputString;
            bool bOk = true;
            int colCount = 1;
            int rowCount = 0;
            while (inputString != "")
            {
               if (inputString.StartsWith("#"))
                  inputString = inputString.Substr(1);
               else if (inputString.StartsWith("|"))
               {
                  int selLength = 0;
                  inputString = inputString.Substr(1);
                  while (!inputString.StartsWith("|") && !inputString.StartsWith("#") && inputString != "")
                  {
                     selLength++;
                     inputString = DecodeCell(inputString, null);
                     if (inputString == null)
                     {
                        bOk = false;
                        break;
                     }
                  }
                  colCount = selLength;
               }
               else
                  bOk = false;
               if (!bOk) break;
               rowCount++;
            }
            if (bOk)
            {
               inputString = copy;
               for (int i = 0; i < BOARD_SIZE_Y; i++)
               {
                  for (int j = 0; j < BOARD_SIZE_X; j++)
                  {
                     cells[i][j].pOperation = -1;
                     cells[i][j].pMovement = -1;
                     cells[i][j].pCharacter = -1;
                     cells[i][j].pAltMovement = -1;
                  }
               }
               for (int i = 0; i < rowCount; i++)
               {
                  if (inputString.StartsWith("#"))
                     inputString = inputString.Substr(1);
                  else
                  {
                     inputString = inputString.Substr(1);
                     for (int j = 0; j < colCount; j++)
                        inputString = DecodeCell(inputString, cells[i][j]);
                  }
               }
            }
            else
               Script.Alert("Input string was not in a correct format");
         }
      }

      private static string DecodeCell(string inputString, StateCell target)
      {
         if (inputString.Substr(0, 1) == ".")
         {
            inputString = inputString.Substr(1);
         }
         else
         {
            int ch = int.Parse(inputString.Substr(0, 1), 36);
            int mo = int.Parse(inputString.Substr(1, 1), 36);
            int op = int.Parse(inputString.Substr(2, 1), 36);
            if (Number.IsNaN(op) || Number.IsNaN(mo) || Number.IsNaN(ch) ||
                        op == 0 || mo == 0 || ch == 0)
            {
               return null;
            }
            if (target != null)
            {
               if (op >= 15)
               {
                  target.pOperation = (ch < 8) ? 0 : 6;
                  target.pAltMovement = op - 16;
                  if (target.pOperation == 6)
                     ch -= 8;
               }
               else
               {
                  target.pOperation = op - 2;
                  target.pAltMovement = -1;
               }
               target.pMovement = mo - 2;
               target.pCharacter = ch - 2;
            }
            inputString = inputString.Substr(3);
         }
         return inputString;
      }

      public string GetBoardAsString()
      {
         return GetPartAsString(0, 0, BOARD_SIZE_X - 1, BOARD_SIZE_Y - 1);
      }

      private void LoadBoardFromStringNewMethod(string input)
      {
         bool bWithColor = input.Substr(0, 1) == "?";
         string dimensions = input.Substr(1, 4);
         string board = LZWDecoder.Decompress(input.Substr(5));
         List<int> sizeBoard = LZWDecoder.DecodeCodeList(dimensions);
         int sizeX = sizeBoard[1];
         int sizeY = sizeBoard[0];
         for (int i = 0; i < sizeY; i++)
         {
            for (int j = 0; j < sizeX; j++)
            {
               cells[i][j].pOperation = int.Parse(board.Substr(0, 1), 36) - 1;
               cells[i][j].pMovement = int.Parse(board.Substr(1, 1), 36) - 1;
               cells[i][j].pCharacter = int.Parse(board.Substr(2, 1), 36) - 1;
               cells[i][j].pAltMovement = int.Parse(board.Substr(3, 1), 36) - 1;
               if (m_abilities.pColorizeEnabled && bWithColor)
               {
                  cells[i][j].pBkgNormalColor = int.Parse(board.Substr(4, 1), 36);
               }
               board = board.Substr(bWithColor ? 5 : 4);
            }
         }
      }

      private void LoadBlockFromString(string input)
      {
         if (input.StartsWith("?") || input.StartsWith("!"))
         {
            bool bWithColor = input.Substr(0, 1) == "?";
            string dimensions = input.Substr(1, 4);
            string board = LZWDecoder.Decompress(input.Substr(5));
            List<int> sizeBoard = LZWDecoder.DecodeCodeList(dimensions);
            int sizeX = sizeBoard[1];
            int sizeY = sizeBoard[0];

            m_bActiveSelection = true;
            m_selFromX = 0;
            m_selFromY = 0;
            m_selToX = sizeX - 1;
            m_selToY = sizeY - 1;
            UpdateSelectionFrame();
            m_selectionMirror.pObject.Empty();

            m_selection = new StateCell[m_selToY - m_selFromY + 1][];
            for (int y = m_selFromY; y < m_selToY + 1; y++)
            {
               m_selection[y - m_selFromY] = new StateCell[m_selToX - m_selFromX + 1];
               for (int x = m_selFromX; x < m_selToX + 1; x++)
               {
                  StateCell copiedCell = new StateCell(m_selectionMirror.pObject);
                  copiedCell.pbHighlighted = false;
                  copiedCell.pPosX = (x - m_selFromX) * DISTANCE_CELL;
                  copiedCell.pPosY = (y - m_selFromY) * DISTANCE_CELL;
                  copiedCell.pOperation = int.Parse(board.Substr(0, 1), 36) - 1;
                  copiedCell.pMovement = int.Parse(board.Substr(1, 1), 36) - 1;
                  copiedCell.pCharacter = int.Parse(board.Substr(2, 1), 36) - 1;
                  copiedCell.pAltMovement = int.Parse(board.Substr(3, 1), 36) - 1;
                  if (m_abilities.pColorizeEnabled && bWithColor)
                  {
                     copiedCell.pBkgNormalColor = int.Parse(board.Substr(4, 1), 36);
                  }
                  board = board.Substr(bWithColor ? 5 : 4);
                  m_selection[y - m_selFromY][x - m_selFromX] = copiedCell;
               }
            }
            m_selectionMirror.pPosY = m_selFromY * DISTANCE_CELL;
            m_selectionMirror.pPosX = m_selFromX * DISTANCE_CELL;
            m_selectionMirror.pObject.CSS("width", ((((m_selToX - m_selFromX) + 1) * DISTANCE_CELL) - 2) + "px");
            m_selectionMirror.pObject.CSS("height", ((((m_selToY - m_selFromY) + 1) * DISTANCE_CELL) - 2) + "px");
            m_selectionMirror.pObject.Show();
            CalcPixelPosOfSelection();
            m_bDontRemoveSelectionOnMove = true;
            m_btnDelete.pEnabled = true;
            for (int clBtn = 0; clBtn < 6; clBtn++)
               m_btnChangeColor[clBtn].pEnabled = m_btnDelete.pEnabled;
            m_btnSaveSelection.pEnabled = true;
         }
      }



      public string GetPartAsString(int fromX, int fromY, int toX, int toY)
      {
         string board = "";
         for (int i = fromY; i <= toY; i++)
         {
            for (int j = fromX; j <= toX; j++)
            {
               board += (cells[i][j].pOperation + 1).ToString(36);
               board += (cells[i][j].pMovement + 1).ToString(36);
               board += (cells[i][j].pCharacter + 1).ToString(36);
               board += (cells[i][j].pAltMovement + 1).ToString(36);
               if (m_abilities.pColorizeEnabled)
                  board += cells[i][j].pBkgNormalColor.ToString(36);
            }
         }
         board = LZWDecoder.Compress(board);

         string res;
         if (m_abilities.pColorizeEnabled)
            res = "?";
         else
            res = "!";
         List<int> sizeBoard = new List<int>();
         sizeBoard.Add(toY - fromY + 1);
         sizeBoard.Add(toX - fromX + 1);
         res += LZWDecoder.EncodeCodeList(sizeBoard);

         return res + board;
      }

      void m_btnLoad_OnClick(TMButton sender)
      {
         m_bLoadMode = true;
         m_dataDlg.pLabelText = "Enter the string with the code to load to the machine";
         m_dataDlg.pEditText = "";
         m_dataDlg.Show();
      }

      void m_btnLoadBlock_OnClick(TMButton sender)
      {
         m_bLoadBlockMode = true;
         m_dataDlg.pLabelText = "Enter the string with the code to load to a new block";
         m_dataDlg.pEditText = "";
         m_dataDlg.Show();
      }

      void m_btnSave_OnClick(TMButton sender)
      {
         m_bLoadMode = false;
         m_dataDlg.pLabelText = "This string contains the code of the current machine. You can paste it again later to restore the current status";
         m_dataDlg.pEditText = GetBoardAsString();
         m_dataDlg.Show();
      }

      #endregion



   }
}
